/*
*  Copyright (c) 2006-2020, Chukie
*
*  SPDX-License-Identifier: Apache-2.0
*
*  @file     : cellular.c
*
*  @brief    :
*  Website   : https://gitee.com/open-embedded
*
*
*  Change Logs:
*  Date           Author          Notes
*  2020-06-23     zhaocj       The first version
*/
/* Includes -----------------------------------------------------------------*/
#include "cellular.h"
#include "cellular_comm.h"
//#define CE_DEBUG
#define DBG_TAG     "CELLULAR"
#include "cellular_log.h"
/* Private typedef ----------------------------------------------------------*/
/* Private define -----------------------------------------------------------*/
/* Private variables --------------------------------------------------------*/
rt_slist_t device_list;
/* Extern variables ---------------------------------------------------------*/
/* Private function prototypes ----------------------------------------------*/
#define device_list_isempty()        rt_slist_isempty(&device_list)
#define device_list_foreach(node)    for ((node) = rt_slist_first(&device_list); (node); (node) = rt_slist_next(node))
#define device_list_append(c)        rt_slist_append(&device_list, &(c)->list)
#define device_list_remove(c)        rt_slist_remove(&device_list, &(c)->list)
#define device_list_entry(node)      rt_slist_entry(node, struct cellular_device, list)

static int cellular_device_component_init(void)
{
    rt_slist_init(&device_list);
    //LOG_I("Modem device component init ok!");
    return RT_EOK;
}
INIT_PREV_EXPORT(cellular_device_component_init);

static int cellular_device_init(cellular_device_t dev)
{
    RT_ASSERT(dev);
#ifdef CELLULAR_USING_CMUX    
    if (RT_EOK != cmux_init(&dev->cmux, CELLULAR_UART_DEVICE_NAME, 0))
    {
        CLOG_ERR("Init cmux failed!");
    }
    else
    {
        if (strlen(CELLULAR_CMUX_PPP_NAME))
        {
            if (RT_EOK != cmux_add_channel(&dev->cmux, CELLULAR_CMUX_PPP_NAME, RT_DEVICE_FLAG_DMA_RX, 4096))
            {
                CLOG_ERR("add ppp cmux port failed!");
            }
        }
        if (strlen(CELLULAR_CMUX_AT_NAME))
        {
            if (RT_EOK != cmux_add_channel(&dev->cmux, CELLULAR_CMUX_AT_NAME, RT_DEVICE_FLAG_INT_TX, 1024))
            {
                CLOG_ERR("Add AT cmux port failed!");
            }
        }
    }
#endif /*CELLULAR_USING_CMUX*/    
    if(dev->ops->init)
    {
        return dev->ops->init(dev);
    }
    return RT_EOK;
}
/* Public function prototypes -----------------------------------------------*/
/**
 * @brief register cellular device
 *
 * @param dev
 * @return int
 */
int cellular_device_register(cellular_device_t dev, const char *name)
{
    RT_ASSERT(dev);
    RT_ASSERT(name);
    RT_ASSERT(dev->ops);
    extern int cellular_ppp_init(cellular_device_t dev);
    snprintf(dev->module_info.name, sizeof(dev->module_info.name), "%s", name);
#ifdef CELLULAR_USING_SLEEP_CTRL
    char sem_name[RT_NAME_MAX] = {0};
    dev->sleep_ctrl.wakeup_lock = 0;
    dev->sleep_ctrl.sleep_interval = 5000;
    snprintf(sem_name, sizeof(sem_name), "sem_%s", name);
    rt_sem_init(&(dev->sleep_ctrl.wakeup_sem), sem_name, 0, RT_IPC_FLAG_PRIO);
#endif /*CELLULAR_USING_SLEEP_CTRL*/

    cellular_ppp_init(dev);
    device_list_append(dev);
    CLOG_NOTICE("Register %s ok!", name);
    return RT_EOK;
}

/**
 * @brief 控制模组开关机
 *
 * @param dev 设备指针
 * @param port 指定下发AT指令所使用的端口
 * @param timeout 操作超时时间,单位秒
 * @param args  0: 关机; 1: 开机
 * @return cellular_device_t reference to cellular_device_t
 */
int cellular_module_power(cellular_device_t dev, cellular_port_type_t port, uint32_t timeout, void *args)
{
    int ret = 0;
    RT_ASSERT((dev && dev->ops && dev->ops->control));
#ifdef CELLULAR_USING_SLEEP_CTRL
    dev->sleep_ctrl.wakeup_lock = 0;
#endif /*CELLULAR_USING_SLEEP_CTRL*/
    if (!!args)
    {
        //Note: 仅允许在关机状态下开机
        if (dev->module_info.state >= MODULE_ON)
        {
            CLOG_WARN("Cellular has started");
            return RT_EOK;
        }
    }
    else
    {
        //Note: 不允许在复位状态下关机
        if (MODULE_RESETING == dev->module_info.state)
        {
            CLOG_WARN("Cellular is resting, can't be closed!");
            return -RT_EBUSY;
        }
        if (MODULE_OFF == dev->module_info.state)
        {
            return RT_EOK;
        }
    }

    ret = dev->ops->control(dev, CELLULAR_CMD_POWER, port, timeout, args);

    if (CELLULAR_OK == ret)
    {
        if ((bool)args)
        {
            cellular_comm_set_module_state(dev, MODULE_ON);
        }
        else
        {
            cellular_comm_set_module_state(dev, MODULE_OFF);
            cellular_hw_set_power(0);
            sys_feed_watchdog();
            CLOG_INFO("Waiting 10s while power down cellular...");
            sleep(10);
        }
    }

    return ret;
}

/**
 * @brief find and attach cellular device by name
 *
 * @param name cellular device name
 * @param proc_cb Processing status callback function
 * @param usr_args user args used in proc_cb
 * @return cellular_device_t
 */
cellular_device_t cellular_find_attach(const char *name, int (*proc_cb)(int status, void *args), void *usr_args)
{
    rt_slist_t *node;
    cellular_device_t dev;
    device_list_foreach(node)
    {
        dev = device_list_entry(node);
        if (!strcmp(dev->module_info.name, name))
        {
            dev->proc_cb = proc_cb;
            dev->usr_args = usr_args;
            cellular_device_init(dev);
            return dev;
        }
    }
    CLOG_WARN("Can't find cellular device named %s", name);
    return NULL;
}

/**
 * @brief 获取唤醒锁
 *
 * @param dev 设备指针
 * @return cellular_device_t reference to cellular_device_t
 */
int cellular_module_wakeup_lock(cellular_device_t dev)
{
    RT_ASSERT((dev && dev->ops && dev->ops->control));
#ifdef CELLULAR_USING_SLEEP_CTRL
    if (dev->sleep_ctrl.enable)
    {
        rt_base_t level = rt_hw_interrupt_disable();
        dev->sleep_ctrl.wakeup_lock++;
        dev->sleep_ctrl.wakeup_ts = uptimem();
        rt_hw_interrupt_enable(level);
        CLOG_DBG("dev->sleep_ctrl.wakeup_lock: %d", dev->sleep_ctrl.wakeup_lock);
        if (dev->ops->control(dev, CELLULAR_CMD_SLEEP_CTRL, CELLULAR_PORT_TYPE_PPP, 10, (void *)0) == RT_EOK)
        {
            level = rt_hw_interrupt_disable();
            dev->sleep_ctrl.wakeup_ts = uptimem();
            rt_hw_interrupt_enable(level);
        }
        else
        {
            return -RT_ERROR;
        }
    }
#endif /*CELLULAR_USING_SLEEP_CTRL*/
    return RT_EOK;
}

/**
 * @brief 释放唤醒锁
 *
 * @param dev 设备指针
 * @return cellular_device_t reference to cellular_device_t
 */
int cellular_module_wakeup_unlock(cellular_device_t dev)
{
    RT_ASSERT((dev && dev->ops && dev->ops->control));
#ifdef CELLULAR_USING_SLEEP_CTRL
    rt_base_t level = rt_hw_interrupt_disable();
    if (dev->sleep_ctrl.wakeup_lock > 0)
    {
        dev->sleep_ctrl.wakeup_lock--;
    }
    rt_hw_interrupt_enable(level);
    CLOG_DBG("dev->sleep_ctrl.wakeup_lock: %d", dev->sleep_ctrl.wakeup_lock);
#endif /*CELLULAR_USING_SLEEP_CTRL*/
    return RT_EOK;
}

/**
 * @brief 休眠模组,该函数会检测唤醒锁，只有所有线程都释放了唤醒锁后才会执行休眠操作
 *
 * @param dev 设备指针
 * @return cellular_device_t reference to cellular_device_t
 */
int cellular_module_sleep(cellular_device_t dev)
{
    RT_ASSERT((dev && dev->ops && dev->ops->control));
#ifdef CELLULAR_USING_SLEEP_CTRL
    if (0 == dev->sleep_ctrl.wakeup_lock && dev->sleep_ctrl.enable)
    {
        if (time_after(uptimem(), dev->sleep_ctrl.wakeup_ts + dev->sleep_ctrl.sleep_interval))
        {
            rt_base_t level = rt_hw_interrupt_disable();
            dev->sleep_ctrl.wakeup_ts = uptimem();
            rt_hw_interrupt_enable(level);
            return dev->ops->control(dev, CELLULAR_CMD_SLEEP_CTRL, CELLULAR_PORT_TYPE_PPP, 10, (void *)1);
        }
    }

#endif /*CELLULAR_USING_SLEEP_CTRL*/
    return RT_EOK;
}

const char *cellular_network_mode2str(network_mode_t mode)
{
    const char *modestr = "Auto";
    switch (mode)
    {
    case NETWORK_MODE_4G_ONLY:
        modestr = "4G(LTE) Only";
        break;
    case NETWORK_MODE_3G_UMTS:
        modestr = "3G(UMTS) Only";
        break;
    case NETWORK_MODE_2G_GSM:
        modestr = "2G(GSM) Only";
        break;
    case NETWORK_MODE_GSM_UMTS:
        modestr = "3G/2G(UMTS/GSM)";
        break;
    case NETWORK_MODE_CAT_M1:
        modestr = "CatM1 Only";
        break;
    case NETWORK_MODE_CAT_NB:
        modestr = "CatNB Only";
        break;
    case NETWORK_MODE_3G_HDR:
        modestr = "3G(HDR) Only";
        break;
    case NETWORK_MODE_2G_CDMA:
        modestr = "2G(CDMA) Only";
        break;
    case NETWORK_MODE_CDMA_HDR:
        modestr = "3G/2G(HDR/CDMA)";
        break;
    case NETWORK_MODE_AUTO:
    default:
        modestr = "Auto";
        break;
    }
    return modestr;
}

#ifdef CELLULAR_USING_CMD
rt_inline void show_cmd_help(void)
{
    rt_kprintf("cellular show <info|config>: show cellular info or configure\n");
    rt_kprintf("    cellular show info: show cellular status info\n");
    rt_kprintf("    cellular show config: show cellular configure\n");
    rt_kprintf("\n");
}

static void cellular_show_info(cellular_device_t dev)
{
    rt_kprintf("Cellular %s status:\n", dev->module_info.name);
    if (dev->module_info.state == MODULE_OFF)
    {
        rt_kprintf("    %-16s: Closed\n", "Cellular Status");
        return;
    }
    rt_kprintf("    %-16s: %s\n", "Cellular IMEI", dev->module_info.imei);
    rt_kprintf("    %-16s: %s\n", "Cellular version", dev->module_info.version);
    rt_kprintf("    %-16s: SIM%d\n", "Used SIM", cellular_get_used_sim_idx(dev) + 1);
    rt_kprintf("    %-16s: %s\n", "SIM IMSI", cellular_get_used_sim_info(dev)->imsi);
    rt_kprintf("    %-16s: %s\n", "SIM ICCID", cellular_get_used_sim_info(dev)->iccid);
    rt_kprintf("    %-16s: %s\n", "SIM Number", cellular_get_used_sim_info(dev)->number);
    rt_kprintf("    %-16s: %ddBm(%d)\n", "Signal", cellular_get_cell_access_info(dev)->rssi, \
               cellular_get_cell_access_info(dev)->csq);

    rt_kprintf("    %-16s: %s\n", "Register state", cellular_reg_state_2_str(cellular_get_cell_access_info(dev)->reg));

    rt_kprintf("    %-16s: %03d%02d\n", "MCCMNC", cellular_get_cell_access_info(dev)->mcc, \
               cellular_get_cell_access_info(dev)->mnc);
    rt_kprintf("    %-16s: %s\n", "Operator", cellular_get_cell_access_info(dev)->operator_name);
    rt_kprintf("    %-16s: %s\n", "Band", cellular_get_cell_access_info(dev)->band_name);
    if (NETWORK_ACT_CDMA == cellular_get_cell_access_info(dev)->act)
    {
        rt_kprintf("    %-16s: bid:0x%X; nid:0x%X; sid:0x%X \n", "Cell info", cellular_get_cell_access_info(dev)->cellid, \
                   cellular_get_cell_access_info(dev)->lac_nid.nid_sid.nid, \
                   cellular_get_cell_access_info(dev)->lac_nid.nid_sid.sid);
    }
    else
    {
        rt_kprintf("    %-16s: cellid:0x%X; lac:0x%X\n", "Cell info", cellular_get_cell_access_info(dev)->cellid, \
                   cellular_get_cell_access_info(dev)->lac_nid.lac);
    }

    if (cellular_ppp_get_state(dev))
    {
        time_t day = (uptime() - dev->ppp.connected_ts) / (24 * 3600);
        time_t hour = ((uptime() - dev->ppp.connected_ts) % (24 * 3600)) / 3600;
        time_t minut = ((uptime() - dev->ppp.connected_ts) % 3600) / 60;
        time_t sec = (uptime() - dev->ppp.connected_ts) % 60;
        uint32_t ip = htonl(cellular_ppp_get_ip(dev));
        rt_kprintf("    %-16s: %s%d day %d:%d:%d\n", "PPP state", "connected ", day, hour, minut, sec);
        rt_kprintf("    %-16s: %d.%d.%d.%d\n", "IP", ((uint8_t *)(&ip))[0], ((uint8_t *)(&ip))[1], ((uint8_t *)(&ip))[2], ((uint8_t *)(&ip))[3]);
    }
    else
    {
        rt_kprintf("    %-16s: %s\n", "PPP state", "Not connected");
        rt_kprintf("    %-16s: 0.0.0.0\n", "IP");
    }
}

static void cellular_show_config(cellular_device_t dev)
{
    dial_config_t cfg = cellular_get_used_dial_config(dev);
    rt_kprintf("Cellular %s config:\n", dev->module_info.name);
    
    rt_kprintf("    %-16s: SIM%d\n", "Used SIM", cellular_get_used_sim_idx(dev) + 1);
    rt_kprintf("    %-16s: %s\n", "Dial number", cfg->ppp_cfg.dial_number);
    rt_kprintf("    %-16s: %s\n", "APN", cfg->apn);
    rt_kprintf("    %-16s: %s\n", "Username", cfg->ppp_cfg.user_name);
    rt_kprintf("    %-16s: %s\n", "Password", cfg->ppp_cfg.password);
    rt_kprintf("    %-16s: %s\n", "PPP Auth Type", cellular_ppp_auth2str(cfg->ppp_cfg.auth));

    rt_kprintf("    %-16s: %s\n", "Network mode", cellular_network_mode2str(cfg->network_mode));

    
}

static void cellular(uint8_t argc, char **argv)
{
    rt_slist_t *node;
    cellular_device_t dev;
    if (argc < 3)
    {
        show_cmd_help();
        return ;
    }
    if (!strncmp(argv[1], "sh", 2))
    {
        if(!strncmp(argv[2],"in",2))
        {
            device_list_foreach(node)
            {
                dev = device_list_entry(node);
                cellular_show_info(dev);
            }
            
        }
        else if(!strncmp(argv[2],"co",2))
        {
            device_list_foreach(node)
            {
                dev = device_list_entry(node);
                cellular_show_config(dev);
            }
        }
        else
        {
            show_cmd_help();
        }
    }
    else
    {
        show_cmd_help();
    }

    rt_kprintf("\n");
}
MSH_CMD_EXPORT(cellular, Show cellular info or set cellular configure);
#endif /*CELLULAR_USING_CMD*/
/* End of file****************************************************************/

